﻿#include "stdafx.h"
#if _QG_USE_DX9
#include "qg_dx9_dev.h"
#include "qg_dx9_mtrl.h"
#include "qg_dx9_draw.h"
#include "qg_dx9_cnv.h"
#include "qg_trace.h"
#include "qg_math.h"
#include "extend/sbvalues.h"

//////////////////////////////////////////////////////////////////////////
// DX9 디바이스
KIMPL_SUBCLASS(DX9Device, "RenderDeviceDX9");

namespace _dx9
{
	// 타입
	kType DevTypeDX9()
	{
		return DX9Device::Type();
	}

	// 2D 그리기 꼭지점 정보
	struct Vertex2D
	{
		tpl::Vec4		pos;
		tpl::Vec2		tex;
		kuint			color;

		void Set(float x, float y, float u, float v, kuint cc)
		{
			pos.Set(x, y, 0.0f, 1.0f);
			tex.Set(u, v);
			color = cc;
		}
	};

	//
	static kModule* ModuleDX9 = NULL;
	static kModule* ModuleD3DX9 = NULL;
}

//
DX9Device::pD3DCreate DX9Device::D3DCREATE = NULL;
DX9Device::pD3DCreateEx DX9Device::D3DCREATEEX = NULL;

DX9Device::pDXCompileShader DX9Device::DXCOMPILESHADER = NULL;

DX9Device::pD3DXCreateCubeTextureFromFileExW DX9Device::DXCREATECUBEFROMFILEEX = NULL;



//
void DX9Device::TypeInitialize(kType type) throw()
{
#if _SB_DEBUG_
	_dx9::ModuleDX9 = k_mod_open("d3d9d.dll", NULL, 0);
	if (!_dx9::ModuleDX9)
#endif
		_dx9::ModuleDX9 = k_mod_open("d3d9.dll", NULL, 0);

	if (_dx9::ModuleDX9)
	{
		k_mod_sym_func(_dx9::ModuleDX9, "Direct3DCreate9", (kfunc*)&D3DCREATE);
		k_mod_sym_func(_dx9::ModuleDX9, "Direct3DCreate9Ex", (kfunc*)&D3DCREATEEX);
	}

	// [2014-4-5 KIM] D3DX_?? -> 25 ~ 43
	for (kint n = D3DX_SDK_VERSION; n > 24; n--)
	{
		char sz[64];
		k_snprintf(sz, 63, "d3dx9_%d.dll", n);

		if ((_dx9::ModuleD3DX9 = k_mod_open(sz, NULL, 0)) != NULL)
			break;
	}

	if (_dx9::ModuleD3DX9)
	{
		k_mod_sym_func(_dx9::ModuleD3DX9, "D3DXCompileShader", (kfunc*)&DXCOMPILESHADER);
		k_mod_sym_func(_dx9::ModuleD3DX9, "D3DXCreateCubeTextureFromFileExW", (kfunc*)&DXCREATECUBEFROMFILEEX);
	}
}

//
void DX9Device::TypeFinalize(kType type) throw()
{
	if (_dx9::ModuleD3DX9)
		k_mod_close(_dx9::ModuleD3DX9);

	if (_dx9::ModuleDX9)
		k_mod_close(_dx9::ModuleDX9);
}

//
DX9Device::DX9Device()
: _rlock(KLOCK_INIT)
{
}

//
DX9Device::~DX9Device()
{
	//
	_Finalize();

	//
	kunk_release(_depthstencil);
	kunk_release(_surface);

	kunk_release(_device);
	kunk_release(_driver);
}

//
bool DX9Device::_CreateDevice(const DeviceCreationData& data, D3DPRESENT_PARAMETERS& dpp, DWORD flags) throw()
{
	HRESULT hr = _driver->CreateDevice(data.adapter, data.hal, data.hwnd,
		D3DCREATE_MULTITHREADED | flags, &dpp, (IDirect3DDevice9**)&_device);

	if (SUCCEEDED(hr))
		return true;
	else
	{
		kobj_tracef(KLV_ERROR, "HRESULT: %08X", hr);
		return false;
	}
}

//
bool DX9Device::_CreateDeviceEx(const DeviceCreationData& data, D3DPRESENT_PARAMETERS& dpp, D3DDISPLAYMODEEX* ddm, DWORD flags) throw()
{
	HRESULT hr = _driver->CreateDeviceEx(data.adapter, data.hal, data.hwnd,
		D3DCREATE_MULTITHREADED | flags, &dpp, ddm, &_device);

	if (SUCCEEDED(hr))
		return true;
	else
	{
		kobj_tracef(KLV_ERROR, "HRESULT: %08X", hr);
		return false;
	}
}

//
bool DX9Device::_Initialize(qgStub* stub, kint flags)
{
	k_return_value_if_fail(D3DCREATE, false);

	tpl::Point size(stub->GetSize());

	// 드라이버
#if 1
	// [2014-3-3 KIM] EX는 MANAGED를 못쓴다... 
	// 어케 고치지
	if (k_sisd_ossub() < KSIPFS_WINDOWS_VISTA || !D3DCREATEEX)
	{
		_driver = (IDirect3D9Ex*)D3DCREATE(D3D_SDK_VERSION);
		if (!_driver)
			return false;

		_vins.exmode = false;
	}
	else if (D3DCREATEEX)
	{
		HRESULT hr = D3DCREATEEX(D3D_SDK_VERSION, &_driver);
		if (FAILED(hr))
			return false;

		_vins.exmode = true;
	}
	else
	{
		// 헐.. 어케 만들라고
		return false;
	}
#else
	_driver = (IDirect3D9Ex*)D3DCREATE(D3D_SDK_VERSION);
	if (!_driver)
		return false;

	_vins.exmode = false;
#endif

	// 여기서 스크린 검사

	// 디바이스
	DeviceCreationData dcd =
	{
		D3DADAPTER_DEFAULT,
		D3DDEVTYPE_HAL,
		(HWND)stub->GetHandle()
	};

	bool usepf = false;
	D3DFORMAT dfbb, dfds;

	// 컬러 포맷... 여기 있는거는 검사할 필요는 없을 듯
	if (K_OFLAG(flags, QGSDF_DITHER))
	{
		dfbb = D3DFMT_R5G6B5;
		dfds = D3DFMT_D15S1;
	}
	else
	{
		dfbb = D3DFMT_X8R8G8B8;
		dfds = D3DFMT_D24S8;
	}

	// 파라미터
	D3DPRESENT_PARAMETERS dpp = {0, };
	if (K_OFLAG(flags, QGSDF_FULL))
	{
		dpp.BackBufferWidth = size.width;
		dpp.BackBufferHeight = size.height;
		dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
	}
	else
	{
		dpp.Windowed = true;
		dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
	}
	dpp.Flags = D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL;
	dpp.BackBufferFormat = dfbb;
	dpp.BackBufferCount = 1;
	dpp.EnableAutoDepthStencil = true;
	dpp.AutoDepthStencilFormat = dfds;
	dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
	dpp.FullScreen_RefreshRateInHz = 0;	// 풀스크린 문제!!! ddm.RefreshRate와 값이 같아야함

	// 디스플레이 모드
	D3DDISPLAYMODEEX ddm, *pddm;
	if (dpp.Windowed || !_vins.exmode)
	{
		pddm = NULL;
		k_zero_1(&ddm);
	}
	else
	{
		pddm = &ddm;
		ddm.Size = sizeof(D3DDISPLAYMODEEX);
		ddm.Width = size.width;
		ddm.Height = size.height;
		ddm.RefreshRate = 0;		// 이값!!!
		ddm.Format = dfbb;
		ddm.ScanLineOrdering = D3DSCANLINEORDERING_PROGRESSIVE;
	}

	// AA
	// 패스

	// NVPERF
	// 패스

	// 다중 그래픽 카드
	// - NVIDIA OPTIMUS
	// - AMD SWITCHABLE
	// 패스

	// 만들어 보기
	if (!_vins.exmode)
	{
		bool isok = usepf ?
			_CreateDevice(dcd, dpp, D3DCREATE_SOFTWARE_VERTEXPROCESSING) :
			_CreateDevice(dcd, dpp, D3DCREATE_HARDWARE_VERTEXPROCESSING) ||
			_CreateDevice(dcd, dpp, D3DCREATE_MIXED_VERTEXPROCESSING) ||
			_CreateDevice(dcd, dpp, D3DCREATE_SOFTWARE_VERTEXPROCESSING);
		if (!isok)
		{
			kobj_tracet(KLV_ERROR, KERR_FAIL, "cannot create device.");
			return false;
		}
	}
	else
	{
		bool isok = usepf ?
			_CreateDeviceEx(dcd, dpp, pddm, D3DCREATE_SOFTWARE_VERTEXPROCESSING) :
			_CreateDeviceEx(dcd, dpp, pddm, D3DCREATE_HARDWARE_VERTEXPROCESSING) ||
			_CreateDeviceEx(dcd, dpp, pddm, D3DCREATE_MIXED_VERTEXPROCESSING) ||
			_CreateDeviceEx(dcd, dpp, pddm, D3DCREATE_SOFTWARE_VERTEXPROCESSING);
		if (!isok)
		{
			kobj_tracet(KLV_ERROR, KERR_FAIL, "cannot create device.");
			return false;
		}
	}

	// 기능 조사
	D3DCAPS9 dcs;
	if (SUCCEEDED(_driver->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &dcs)))
	{
		// 일단...
		k_strcpy(_caps.renderer, "DX9");
		k_strcpy(_caps.vendor, "Microsoft");

		// 세이더
		_CheckShaderVersion(dcs);

		_caps.supp_shader = true;		// 무조건 지원. 안하면 안댐

		// 스텐실
		if (dfds == D3DFMT_D15S1 ||
			dfds == D3DFMT_D24S8 ||
			dfds == D3DFMT_D24X4S4 ||
			dfds == D3DFMT_D24FS8)
			_caps.supp_stencil = true;

		// 텍스쳐 관련
		_caps.max_texture_count = (kbyte)K_MAX(dcs.MaxTextureBlendStages, 8);	// 8 <- 고정
		_caps.max_offtex_count = (kbyte)K_MAX(dcs.NumSimultaneousRTs, 4);		// 4 <- 고정

		if (dcs.MaxTextureBlendStages > 1)
			_caps.supp_multi_texture = true;

		_caps.supp_compressed_texture = true;		// 무조건 지원, 지원안하면 DX9가 알아서 처리

		_caps.supp_texture_npo2 = (dcs.TextureCaps & (D3DPTEXTURECAPS_POW2 | D3DPTEXTURECAPS_NONPOW2CONDITIONAL)) == 0;

		_caps.max_texture_dimension = K_MIN(dcs.MaxTextureWidth, dcs.MaxTextureHeight);

		_caps.max_texture_LOD_bias = FLT_MAX;

		// 오클루전
		if (SUCCEEDED(_device->CreateQuery(D3DQUERYTYPE_OCCLUSION, NULL)))
			_caps.supp_occlusion = true;

		// 정점
		_caps.max_indices_count = dcs.MaxVertexIndex;
		_caps.max_gs_vertices_count = 0;

		// 기타
		_caps.supp_byte_glyph_texture = false;

		for (ksize_t i = 0; i < 2; i++)
			_caps.dim_alias_line[i] = _caps.dim_alias_point[i] = _caps.dim_smt_line[i] = _caps.dim_smt_point[i] = 0;

		_caps.version = (kushort)(D3D_SDK_VERSION & 0xFFFF);
	}
	else
	{
		// 아 머여... 이건 걍 실패
		kobj_tracet(KLV_ERROR, KERR_DEVICE, "cannot determint device spec.");
		return false;
	}

	//
	_vprn.fmt_back_buffer = dfbb;
	_vprn.fmt_depth_stencil = dfds;

	_vins.dpp = dpp;
	_vins.ddm = ddm;
	_vins.reset = true;
	_vins.lost = false;

	// 정보 표시
	D3DADAPTER_IDENTIFIER9 dai;
	if (SUCCEEDED(_driver->GetAdapterIdentifier(D3DADAPTER_DEFAULT, 0, &dai)))
	{
		kobj_trace(KLV_INFO, "DirectX9 Driver");
		kobj_tracef(KLV_INFO, " - Driver: %s", dai.Description);
		kobj_tracef(KLV_INFO, " - Version : %d.%d.%d.%d",
			HIWORD(dai.DriverVersion.HighPart), LOWORD(dai.DriverVersion.HighPart),
			HIWORD(dai.DriverVersion.LowPart), LOWORD(dai.DriverVersion.LowPart));
		k_strncpy(_caps.vendor, dai.Description, 63);
	}

	// 슈퍼
	return super_type::_Initialize(stub, flags);
}

//
void DX9Device::_CheckShaderVersion(D3DCAPS9& dcs) throw()
{
	kushort major, minor;

	// 정점 세이더
	major = (kushort)((dcs.VertexShaderVersion & 0xFF00) >> 8);
	minor = (kushort)(dcs.VertexShaderVersion & 0xFF);

	bool v2x = false, v2a = false;

	if (major >= 2 &&
		(dcs.VS20Caps.Caps & D3DVS20CAPS_PREDICATION) &&
		(dcs.VS20Caps.DynamicFlowControlDepth > 0))
	{
		if (dcs.VS20Caps.NumTemps >= 12)
			v2x = true;

		if (dcs.VS20Caps.NumTemps >= 13)
			v2a = true;
	}

	switch (major)
	{
		case 1:		// 1.X
			_vprn.cps_vs_max_bool = 0;
			_vprn.cps_vs_max_int = 0;
			_vprn.cps_vs_max_float = (kushort)dcs.MaxVertexShaderConst;
			k_strcpy(_vprn.prof_vs, "vs_1_1");
			break;

		case 2:		// 2.X
			_vprn.cps_vs_max_bool = 16;
			_vprn.cps_vs_max_int = 16;
			_vprn.cps_vs_max_float = (kushort)dcs.MaxVertexShaderConst;
			if (v2a)
				k_strcpy(_vprn.prof_vs, "vs_2_a");
			else if (v2x)
				k_strcpy(_vprn.prof_vs, "vs_2_x");
			else
				k_strcpy(_vprn.prof_vs, "vs_2_0");
			break;

		case 3:		// 3X
			_vprn.cps_vs_max_bool = 16;
			_vprn.cps_vs_max_int = 16;
			_vprn.cps_vs_max_float = (kushort)dcs.MaxVertexShaderConst;
			k_strcpy(_vprn.prof_vs, "vs_3_0");
			break;
	}

	// 픽셀 세이더
	major = (kushort)((dcs.PixelShaderVersion & 0xFF00) >> 8);
	minor = (kushort)(dcs.PixelShaderVersion & 0xFF);

	bool p2x = false, p2b = false, p2a = false;

	if (major >= 2 &&
		(dcs.PS20Caps.Caps & D3DPS20CAPS_NOTEXINSTRUCTIONLIMIT))
	{
		if (dcs.PS20Caps.NumTemps >= 32)
			p2b = true;

		if ((dcs.PS20Caps.Caps & (D3DPS20CAPS_NODEPENDENTREADLIMIT | D3DPS20CAPS_ARBITRARYSWIZZLE | D3DPS20CAPS_GRADIENTINSTRUCTIONS | D3DPS20CAPS_PREDICATION)) &&
			(dcs.PS20Caps.NumTemps >= 22))
			p2a = true;

		if (p2b || p2a)
			p2x = true;
	}

	switch (major)
	{
		case 1:		// 1.X
			_vprn.cps_ps_max_bool = 0;
			_vprn.cps_ps_max_int = 0;
			_vprn.cps_ps_max_float = 8;
			if (minor >= 4)
				k_strcpy(_vprn.prof_ps, "ps_1_4");
			else if (minor == 3)
				k_strcpy(_vprn.prof_ps, "ps_1_3");
			else if (minor == 2)
				k_strcpy(_vprn.prof_ps, "ps_1_2");
			else
				k_strcpy(_vprn.prof_ps, "ps_1_1");
			break;

		case 2:		// 2.X
			_vprn.cps_ps_max_bool = 16;
			_vprn.cps_ps_max_int = 16;
			_vprn.cps_ps_max_float = 32;
			if (p2b)
				k_strcpy(_vprn.prof_ps, "ps_2_b");
			else if (p2a)
				k_strcpy(_vprn.prof_ps, "ps_2_a");
			else if (p2x)
				k_strcpy(_vprn.prof_ps, "ps_2_x");
			else
				k_strcpy(_vprn.prof_ps, "ps_2_0");
			break;

		case 3:		// 3X
			_vprn.cps_ps_max_bool = 16;
			_vprn.cps_ps_max_int = 16;
			_vprn.cps_ps_max_float = 196;
			k_strcpy(_vprn.prof_ps, minor > 0 ? "ps_3_x" : "ps_3_0");
			break;
	}

	// 버전은 PS 버전으로
	_caps.version_shader = (kushort)dcs.PixelShaderVersion;
}

//
bool DX9Device::_ResetPrepare(const tpl::Point& size, bool prepare)
{
	ksize_t i;
	HRESULT hr;

	// DX9용
	_sprn = &_vprn;
	_sdeb = &_vdeb;

	// 기본값
	super_type::_ResetPrepare(size, prepare);

	// TM, 디바이스에 달린 속성 처리, 나머지는 단위행렬로 초기화 되어 있음
	DX9Device::SetTM(QGTMS_PROJ, tpl::Mat4::PerspectiveFovLH(KM_PI_HOH, size.Aspect(), 1.0f, 100000.0f));

	// 상태 리셋

	// 광원 
	_device->SetRenderState(D3DRS_LIGHTING, false);
	_device->SetRenderState(D3DRS_SPECULARENABLE, false);

	// 재질 알파 연산 및 테스트 값

	// 샘플러 & 텍스쳐 & 팩터
	for (i = 0; i < _caps.max_texture_count; i++)
	{
		_vprn.m_splr_data[i] = qgSplrDataEx(QGFLT_NONE, QGADR_NONE, QGADR_NONE, QGADR_NONE, QGCOP_NONE, tpl::ColorValues::Transparent);

		_device->SetTexture(i, NULL);

		_device->SetSamplerState(i, D3DSAMP_MINFILTER, D3DTEXF_POINT);
		_device->SetSamplerState(i, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
		_device->SetSamplerState(i, D3DSAMP_MIPFILTER, D3DTEXF_POINT);
		_device->SetSamplerState(i, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
		_device->SetSamplerState(i, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
		_device->SetSamplerState(i, D3DSAMP_ADDRESSW, D3DTADDRESS_WRAP);
		_device->SetSamplerState(i, D3DSAMP_MIPMAPLODBIAS, 0);
		_device->SetSamplerState(i, D3DSAMP_BORDERCOLOR, 0);

		_device->SetTextureStageState(i, D3DTSS_COLOROP, D3DTOP_DISABLE);
		_device->SetTextureStageState(i, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
	}

	// 래스터라이저
	_vprn.m_rasz_data.fill = QGFILL_SOLID;
	_vprn.m_rasz_data.cull = QGCULL_BACK;
	_vprn.m_rasz_data.dfunc = QGCOP_LEQ;
	_vprn.m_rasz_data.dwrite = true;
	_vprn.m_rasz_data.dbias = 0;
	_vprn.m_rasz_data.dclamp = 0.0f;
	_vprn.m_rasz_data.slope_scale = 0.0f;

	_device->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
	_device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
	_device->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);
	_device->SetRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL);
	_device->SetRenderState(D3DRS_ZWRITEENABLE, D3DZB_TRUE);
	_device->SetRenderState(D3DRS_DEPTHBIAS, 0);

	// 블랜드	
	_vprn.m_blnd_data.blend.op = QGBLO_NONE;
	_vprn.m_blnd_data.blend.src = QGBLV_NONE;
	_vprn.m_blnd_data.blend.dest = QGBLV_NONE;
	_vprn.m_blnd_data.alpha.op = QGBLO_NONE;
	_vprn.m_blnd_data.alpha.src = QGBLV_NONE;
	_vprn.m_blnd_data.alpha.dest = QGBLV_NONE;
	_vprn.m_blnd_data.mask = QGMASK_ALL;
	_vprn.m_blnd_blend = false;
	_vprn.m_blnd_alpha = false;

	_device->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
	_device->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
	_device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
	_device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO);
	_device->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, false);
	_device->SetRenderState(D3DRS_BLENDOPALPHA, D3DBLENDOP_ADD);
	_device->SetRenderState(D3DRS_SRCBLENDALPHA, D3DBLEND_ONE);
	_device->SetRenderState(D3DRS_DESTBLENDALPHA, D3DBLEND_ZERO);
	_device->SetRenderState(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_ALPHA | D3DCOLORWRITEENABLE_RED | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_BLUE);

	// 세이더
	_vprn.m_shd_ps = NULL;
	_vprn.m_shd_vs = NULL;

	_device->SetPixelShader(NULL);
	_device->SetVertexShader(NULL);

	// 그 밖의 설정 - 텍스쳐. 이거 필요... 하겠지... POSITIONT용
	_device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
	_device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
	_device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_CURRENT);	// D3DTA_DIFFUSE
	_device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
	_device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
	_device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_CURRENT);	// D3DTA_DIFFUSE

	// 서피스 저장, 리셋하면 surface가 지워지므로 여기서 해야한다
	kunk_release(_surface);

	hr = _device->GetRenderTarget(0, &_surface);
	if (FAILED(hr))
	{
		if (hr == D3DERR_NOTFOUND)
			_surface = NULL;
		else
		{
			kobj_tracet(KLV_ERROR, KERR_CONTEXT, "render target surface error.");
			return false;
		}
	}

	// 뎁스 - 스텐실 저장, 서피스와 마찬가지의 이유로 여기서함
	kunk_release(_depthstencil);

	hr = _device->GetDepthStencilSurface(&_depthstencil);
	if (FAILED(hr))
	{
		kobj_tracet(KLV_ERROR, KERR_CONTEXT, "depth - stencil surface error.");
		return false;
	}

	// 인터페이스 준비
	if (prepare)
	{
		// 꼭지점 버퍼
		if ((_vdeb.vb = CreateBuffer(QGBUFFER_VERTEX, sizeof(_dx9::Vertex2D), 4, QGRESF_DYNAMIC, NULL)) == NULL)
		{
			kobj_tracep(KLV_CRITICAL, KERR_FAIL_CREATE);
			return false;
		}

		// 레이아웃
		static qgLayoutElement s_le[] =
		{
			{/*POSITIONT*/QGDLOU_MAX_VALUE, 0, QGDLOT_FLOAT4, 0, 0, },
			{QGDLOU_TEXTURE, 0, QGDLOT_FLOAT2, 0, 0, },
			{QGDLOU_COLOR, 0, QGDLOT_UINTCOLOR, 0, 0, },
		};

		if ((_vdeb.lrtc = CreateLayout(s_le)) == NULL)
		{
			kobj_tracep(KLV_CRITICAL, KERR_FAIL_CREATE);
			return false;
		}
	}

	// 그릴 수 있도록 함
	_SetRenderTarget(NULL);

	return true;
}

//
void DX9Device::_Finalize()
{
	// 기본 오브젝트 제거
	kobj_unload(_vdeb.lrtc);
	kobj_unload(_vdeb.vb);

	//
	super_type::_Finalize();
}

//
void DX9Device::_ResetDevice() throw()
{
	tpl::LockGuard<kSpinLock> lock(_rlock);

	if (_vins.reset)
	{
		if (!_vins.exmode)
			_RoamSignal(DX9_SIGNAL_LOST);

		_vins.reset = false;
	}

	HRESULT hr;

	if (!_vins.exmode)
		hr = _device->Reset(&_vins.dpp);
	else
	{
		D3DDISPLAYMODEEX* pddm = _vins.dpp.Windowed || !_vins.exmode ? NULL : &_vins.ddm;
		hr = _device->ResetEx(&_vins.dpp, pddm);
	}

	if (FAILED(hr))
	{
		switch (hr)
		{
			case D3DERR_NOTFOUND:
			case D3DERR_MOREDATA:
			case D3DERR_DEVICELOST:
			case D3DERR_DEVICENOTRESET:
			case D3DERR_NOTAVAILABLE:
			case D3DERR_OUTOFVIDEOMEMORY:
			case D3DERR_INVALIDDEVICE:
			case D3DERR_INVALIDCALL:
			case D3DERR_WASSTILLDRAWING:
			case D3DERR_DRIVERINTERNALERROR:
				break;

			case E_NOTIMPL:	// 원래 프로그램 종료 감인데...
				kobj_tracep(KLV_CRITICAL, KERR_NOTIMPL);
				break;

			default:
				kobj_tracef(KLV_CRITICAL, "unknown reset error on render device. [%X]", hr);
				_stub->Close();
				break;
		}
	}
	else
	{
		_ResetPrepare(_stub->GetSize(), false);

		if (!_vins.exmode)
			_RoamSignal(DX9_SIGNAL_RESET);

		_vins.reset = true;
	}
}

//
bool DX9Device::TestEnableD3DFORMAT(D3DFORMAT fmt, DWORD mask, D3DRESOURCETYPE rtype)
{
	HRESULT hr = _driver->CheckDeviceFormat(
		D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, _vprn.fmt_back_buffer,
		mask, rtype, fmt);
	return SUCCEEDED(hr);
}

//
bool DX9Device::Layout(const qgSevLayout& e)
{
	if (!super_type::Layout(e))
		return false;

	kGuardSpinLock(_dlock)
	{
		//
		_vins.dpp.BackBufferWidth = e.width;
		_vins.dpp.BackBufferHeight = e.height;
		_vins.lost = true;
	}

	_ResetDevice();

	kGuardSpinLock(_dlock)
	{
		HRESULT hr = _device->TestCooperativeLevel();
		if (FAILED(hr))
		{
			if (hr == D3DERR_DEVICENOTRESET)
				_ResetDevice();
		}

		//
		D3DVIEWPORT9 vp;
		vp.X = 0;
		vp.Y = 0;
		vp.Width = e.width;
		vp.Height = e.height;
		vp.MinZ = 0.0f;
		vp.MaxZ = 1.0f;
		_device->SetViewport(&vp);
	}

	DX9Device::SetTM(QGTMS_PROJ, tpl::Mat4::PerspectiveFovLH(KM_PI_HOH, _trfm.vp.aspect, 1.0f, 100000.0f));
	DX9Device::SetTM(QGTMS_VIEW, tpl::Mat4::IdentifyMatrix());
	DX9Device::SetTM(QGTMS_WORLD, tpl::Mat4::IdentifyMatrix());

	// 얘도 잠궈야하나...
	_trfm.otm = tpl::Mat4::OrthoLH(_vsize.width, _vsize.height, 0.0f, 1.0f);

	return true;
}

//
void DX9Device::_InternalClear(kint clearance, const tpl::Color& color, kint stencil, float depth)
{
	if (clearance != 0)
	{
		DWORD dw = 0;

		if (clearance&QGCLEAR_RENDER)
			dw |= D3DCLEAR_TARGET;

		if (clearance&QGCLEAR_STENCIL)
			dw |= D3DCLEAR_STENCIL;

		if (clearance&QGCLEAR_DEPTH)
			dw |= D3DCLEAR_ZBUFFER;

		kGuardSpinLock(_dlock)
			_device->Clear(0, NULL, dw, color.ToUint(), depth, stencil);
	}
}

//
bool DX9Device::_InternalEnter(kint clearance, const tpl::Color& color, kint stencil, float depth)
{
	kGuardSpinLock(_dlock)
	{
		if (_vins.lost)
		{
			if (_stub->GetInsts(QGSDI_EXIT))
				return false;

			HRESULT hr = _device->TestCooperativeLevel();
			if (FAILED(hr))
			{
				if (hr == D3DERR_DEVICENOTRESET)
					_ResetDevice();
				return false;
			}

			_vins.lost = false;
		}
	}

	//
	_InternalClear(clearance, color, stencil, depth);

	//
	kGuardSpinLock(_dlock)
	{
		if (k_atominc(&_vins.enters) == 1)
		{
			// 첫 호출
			return SUCCEEDED(_device->BeginScene());
		}
	}

	// 연속 호출
	return true;
}

//
bool DX9Device::_InternalLeave()
{
	kGuardSpinLock(_dlock)
	{
		if (_vins.lost)
			return false;

		if (k_atomdec(&_vins.enters) == 0)
		{
			// 첫 호출
			return SUCCEEDED(_device->EndScene());
		}
	}

	// 연속 호출
	return true;
}

//
bool DX9Device::Enter(kint clearance)
{
	super_type::Enter(clearance);

	return _InternalEnter(clearance, _sprn->cr_bg, 0, 1.0f);
}

//
bool DX9Device::Leave()
{
	if (!super_type::Leave())
		return false;

	return _InternalLeave();
}

//
bool DX9Device::Flush()
{
	super_type::Flush();

	kGuardSpinLock(_dlock)
	{
		if (_vins.lost)
			return false;

		kint ent = k_atomget(&_vins.enters);
		if (ent != 0)
		{
			// 오류를 출력할려다... 말자
			return false;
		}

		HRESULT hr = _vins.exmode
			? _device->PresentEx(NULL, NULL, NULL, NULL, 0)
			: _device->Present(NULL, NULL, NULL, NULL);
		if (FAILED(hr))
		{
			if (hr == D3DERR_DEVICELOST)
				_vins.lost = true;
			else if (hr == D3DERR_DRIVERINTERNALERROR)
				_vins.lost = true;
			else if (hr != D3DERR_INVALIDCALL)
			{
				// 음
			}
			return false;
		}
	}

	return true;
}

//
void DX9Device::_InternalDraw(kint texcount, kint vertexcount, kint vertexstart)
{
	kGuardSpinLock(_dlock)
	{
		if (!_BindMtrl(texcount) ||
			!_BindShader() ||
			!_BindDraw(false))
		{
			// 설정 실패
			kobj_tracep(KLV_ERROR, KERR_INVAL);
		}
		else
		{
			// 그리기 함수 호출
			D3DPRIMITIVETYPE tpg = DX9Convert::Topology(_sprn->w_tpg);
			DWORD count = DX9Convert::PrimitiveCount(_sprn->w_tpg, vertexcount);

			_device->DrawPrimitive(tpg, (UINT)vertexstart, (UINT)count);
		}
	}
}

//
void DX9Device::_InternalDrawIndexed(kint texcount, kint indexcount, kint vertexstart)
{
	kGuardSpinLock(_dlock)
	{
		if (!_BindMtrl(texcount) ||
			!_BindShader() ||
			!_BindDraw(true))
		{
			// 설정 실패
			kobj_tracep(KLV_ERROR, KERR_INVAL);
		}
		else
		{
			// 그리기 함수 호출
			D3DPRIMITIVETYPE tpg = DX9Convert::Topology(_sprn->w_tpg);
			DWORD count = DX9Convert::PrimitiveCount(_sprn->w_tpg, indexcount);

			_device->DrawIndexedPrimitive(
				tpg, (UINT)vertexstart, 0,
				(UINT)_sprn->w_vb[0]->GetCount(), (UINT)_sprn->w_ioff, (UINT)count);
		}
	}
}

//
void DX9Device::Draw(kint vertexcount, kint vertexstart)
{
	QGTRACE_DEVICE(DX9Device);

	if (!_sprn->w_vb[0] || !_sprn->w_lo)
	{
		// 꼭지점 엄따
		kobj_tracep(KLV_ERROR, KERR_ARGUMENT);
	}
	else
	{
		// 그리기
		_InternalDraw(QGSGTEX_MAX_VALUE, vertexcount, vertexstart);
	}
}

//
void DX9Device::DrawIndexed(kint indexcount, kint vertexstart)
{
	QGTRACE_DEVICE(DX9Device);

	if (!_sprn->w_vb[0] || !_sprn->w_ib || !_sprn->w_lo)
	{
		// 세이더 또는 꼭지점 또는 인덱스 없이 그리려 하다니!!!
		kobj_tracep(KLV_ERROR, KERR_ARGUMENT);
	}
	else
	{
		// 그리기
		_InternalDrawIndexed(QGSGTEX_MAX_VALUE, indexcount, vertexstart);
	}
}

//
void DX9Device::DrawEnterIndirect(kint vertexcount, kint vertexstride, kpointer* outdata)
{
	k_return_if_fail(outdata != NULL);

	QGTRACE_DEVICE(DX9Device);

	kGuardSpinLock(_dlock)
	{
		if (vertexstride < (kint)sizeof(_dx9::Vertex2D))
			vertexstride = (kint)sizeof(_dx9::Vertex2D);

		kint vbs = _vdeb.vb->GetCount() * _vdeb.vb->GetStride();
		kint ins = vertexcount * vertexstride;

		if (vbs < ins)
		{
			if (_sprn->w_vb[0] == _vdeb.vb)
				_sprn->w_vb[0] = NULL;

			_vdeb.vb->Unload();

			if ((_vdeb.vb = CreateBuffer(QGBUFFER_VERTEX, vertexstride, vertexcount + 1, QGRESF_DYNAMIC, NULL)) == NULL)
			{
				kobj_tracep(KLV_CRITICAL, KERR_FAIL_CREATE);
				_vdeb.vb = NULL;
				return;
			}
		}

		//
		_vprn.d_ind_vertexcount = vertexcount;

		//
		*outdata = _vdeb.vb->EnterMap(QGENTF_NONE);
	}
}

//
void DX9Device::DrawLeaveIndirect()
{
	QGTRACE_DEVICE(DX9Device);

	_vdeb.vb->LeaveMap();

	DX9Device::SetVertexBuffer(0, _vdeb.vb);
	DX9Device::_InternalDraw(QGSGTEX_MAX_VALUE, _vprn.d_ind_vertexcount, 0);
	DX9Device::SetVertexBuffer(0, NULL);

	_vprn.d_ind_vertexcount = 0;
}

//
void DX9Device::DrawGlyph(const tpl::Rect&bound, const tpl::ColorValue& color, qgTexture* tex)
{
	QGTRACE_DEVICE(DX9Device);

	k_return_if_ok(color.a < 10);

	// 그리기 데이터 준비
	kuint bgra = color.ToUint();
	tpl::Quad rt = bound.ToQuad().Offset(-0.5f, -0.5f);
	const tpl::Quad& coord = (const tpl::Quad&)_math::Global::TexCoord;

	kGuardSpinLock(_dlock)
	{
		_dx9::Vertex2D* vx = (_dx9::Vertex2D*)_vdeb.vb->EnterMap(QGENTF_NONE);
		vx[0].Set(rt.left, rt.top, coord.left, coord.top, bgra);
		vx[1].Set(rt.right, rt.top, coord.right, coord.top, bgra);
		vx[2].Set(rt.left, rt.bottom, coord.left, coord.bottom, bgra);
		vx[3].Set(rt.right, rt.bottom, coord.right, coord.bottom, bgra);
		_vdeb.vb->LeaveMap();
	}

	// 그리기
	DX9Device::SetRasz(_vdeb.rasz);
	DX9Device::SetBlnd(_vdeb.blnd);
	DX9Device::SetSplr(0, _vdeb.splr);
	DX9Device::SetTexture(0, tex);
	DX9Device::SetShader(NULL);
	DX9Device::SetLayout(_vdeb.lrtc);
	DX9Device::SetVertexBuffer(0, _vdeb.vb);
	DX9Device::SetTopology(QGTPG_TRI_STRIP);
	DX9Device::_InternalDraw(1, 4, 0);
}

//
void DX9Device::DrawRect(const tpl::Rect& bound, const tpl::ColorValue& color, qgTexture* tex, const tpl::Quad* coord)
{
	QGTRACE_DEVICE(DX9Device);

	k_return_if_ok(color.a < 10);

	if (!coord)
		coord = (const tpl::Quad*)&_math::Global::TexCoord;

	// 그리기 데이터
	kuint bgra = color.ToUint();
	tpl::Quad rt = bound.ToQuad().Offset(-0.5f, -0.5f);

	kGuardSpinLock(_dlock)
	{
		_dx9::Vertex2D* vx = (_dx9::Vertex2D*)_vdeb.vb->EnterMap(QGENTF_NONE);
		vx[0].Set(rt.left, rt.top, coord->left, coord->top, bgra);
		vx[1].Set(rt.right, rt.top, coord->right, coord->top, bgra);
		vx[2].Set(rt.left, rt.bottom, coord->left, coord->bottom, bgra);
		vx[3].Set(rt.right, rt.bottom, coord->right, coord->bottom, bgra);
		_vdeb.vb->LeaveMap();
	}

	// 그리기
	DX9Device::SetRasz(_vdeb.rasz);
	DX9Device::SetBlnd(_vdeb.blnd);
	DX9Device::SetSplr(0, _vdeb.splr);
	DX9Device::SetTexture(0, tex);
	DX9Device::SetShader(NULL);
	DX9Device::SetLayout(_vdeb.lrtc);
	DX9Device::SetVertexBuffer(0, _vdeb.vb);
	DX9Device::SetTopology(QGTPG_TRI_STRIP);
	DX9Device::_InternalDraw(1, 4, 0);
}

//
void DX9Device::DrawPolyLine(kint count, const tpl::Point pts[], const tpl::ColorValue colors[])
{
	QGTRACE_DEVICE(DX9Device);

	// 확인
	k_return_if_fail(count >= 2);
	k_return_if_fail(pts);

	// 꼭지점 데이터 만들기
	kGuardSpinLock(_dlock)
	{
		if (_vdeb.vb->GetCount() <= count)
		{
			// 꼭지점 버퍼 개수 늘림
			if (_sprn->w_vb[0] == _vdeb.vb)
				_sprn->w_vb[0] = NULL;

			_vdeb.vb->Unload();

			if ((_vdeb.vb = CreateBuffer(QGBUFFER_VERTEX, sizeof(_dx9::Vertex2D), count + 1, QGRESF_DYNAMIC, NULL)) == NULL)
			{
				kobj_tracep(KLV_CRITICAL, KERR_FAIL_CREATE);
				_vdeb.vb = NULL;
				return;
			}
		}

		// 잠그고 넣기
		_dx9::Vertex2D* vx = (_dx9::Vertex2D*)_vdeb.vb->EnterMap(QGENTF_NONE);

		float x, y;
		kint i;

		if (colors)
		{
			for (i = 0; i < count; i++)
			{
				x = (float)pts[i].x - 0.5f;
				y = (float)pts[i].y - 0.5f;
				vx[i].Set(x, y, 0.0f, 0.0f, colors[i].ToUint());
			}

			x = (float)pts[0].x - 0.5f;
			y = (float)pts[0].y - 0.5f;
			vx[i].Set(x, y, 0.0f, 0.0f, colors[0].ToUint());
		}
		else
		{
			for (i = 0; i < count; i++)
			{
				x = (float)pts[i].x - 0.5f;
				y = (float)pts[i].y - 0.5f;
				vx[i].Set(x, y, 0.0f, 0.0f, 0xFFFFFFFF);
			}

			x = (float)pts[0].x - 0.5f;
			y = (float)pts[0].y - 0.5f;
			vx[i].Set(x, y, 0.0f, 0.0f, 0xFFFFFFFF);
		}

		_vdeb.vb->LeaveMap();
	}

	// 그리기
	DX9Device::SetRasz(_vdeb.rasz);
	DX9Device::SetBlnd(_vdeb.blnd);
	DX9Device::SetSplr(0, _vdeb.splr);
	DX9Device::SetTexture(0, NULL);
	DX9Device::SetShader(NULL);
	DX9Device::SetLayout(_vdeb.lrtc);
	DX9Device::SetVertexBuffer(0, _vdeb.vb);
	DX9Device::SetTopology(QGTPG_LINE_STRIP);
	DX9Device::_InternalDraw(1, count + 1, 0);
}

//
void DX9Device::DrawPolyStrip(kint count, const tpl::Point* pts, const tpl::ColorValue* colors, qgTexture* tex, const tpl::Vec2* coords)
{
	QGTRACE_DEVICE(DX9Device);

	// 확인
	k_return_if_fail(count >= 3);
	k_return_if_fail(pts);

	// 꼭지점 데이터 만들기
	kGuardSpinLock(_dlock)
	{
		if (_vdeb.vb->GetCount() <= count)
		{
			// 꼭지점 버퍼 개수 늘림
			if (_sprn->w_vb[0] == _vdeb.vb)
				_sprn->w_vb[0] = NULL;

			_vdeb.vb->Unload();

			if ((_vdeb.vb = CreateBuffer(QGBUFFER_VERTEX, sizeof(_dx9::Vertex2D), count + 1, QGRESF_DYNAMIC, NULL)) == NULL)
			{
				kobj_tracep(KLV_CRITICAL, KERR_FAIL_CREATE);
				_vdeb.vb = NULL;
				return;
			}
		}

		// 잠그고 넣기
		_dx9::Vertex2D* vx = (_dx9::Vertex2D*)_vdeb.vb->EnterMap(QGENTF_NONE);

		float x, y;
		kint i;

		if (colors)
		{
			if (coords)
			{
				for (i = 0; i < count; i++)
				{
					x = (float)pts[i].x - 0.5f;
					y = (float)pts[i].y - 0.5f;
					vx[i].Set(x, y, coords[i].u, coords[i].v, colors[i].ToUint());
				}
			}
			else
			{
				for (i = 0; i < count; i++)
				{
					x = (float)pts[i].x - 0.5f;
					y = (float)pts[i].y - 0.5f;
					vx[i].Set(x, y, 0.0f, 0.0f, colors[i].ToUint());
				}
			}
		}
		else
		{
			if (coords)
			{
				for (i = 0; i < count; i++)
				{
					x = (float)pts[i].x - 0.5f;
					y = (float)pts[i].y - 0.5f;
					vx[i].Set(x, y, coords[i].u, coords[i].v, 0xFFFFFFFF);
				}
			}
			else
			{
				for (i = 0; i < count; i++)
				{
					x = (float)pts[i].x - 0.5f;
					y = (float)pts[i].y - 0.5f;
					vx[i].Set(x, y, 0.0f, 0.0f, 0xFFFFFFFF);
				}
			}
		}

		_vdeb.vb->LeaveMap();
	}

	// 그리기
	DX9Device::SetRasz(_vdeb.rasz);
	DX9Device::SetBlnd(_vdeb.blnd);
	DX9Device::SetSplr(0, _vdeb.splr);
	DX9Device::SetTexture(0, tex);
	DX9Device::SetShader(NULL);
	DX9Device::SetLayout(_vdeb.lrtc);
	DX9Device::SetVertexBuffer(0, _vdeb.vb);
	DX9Device::SetTopology(QGTPG_TRI_STRIP);
	DX9Device::_InternalDraw(1, count, 0);
}

//
bool DX9Device::_SetRenderTarget(qgTexture* tex)
{
	tpl::Point size;

	if (tex)
	{
		DX9Texture* dtex = (DX9Texture*)tex;

		size.Set(dtex->_width, dtex->_height);
	}
	else
	{
		IDirect3DSurface9* surface;
		HRESULT hr;

		if (_surface)
		{
			hr = _device->GetRenderTarget(0, &surface);
			if (FAILED(hr))
				_device->SetRenderTarget(0, _surface);
			else
			{
				if (surface != _surface)
					_device->SetRenderTarget(0, _surface);

				surface->Release();
			}
		}

		if (_depthstencil)
		{
			hr = _device->GetDepthStencilSurface(&surface);
			if (FAILED(hr))
				_device->SetDepthStencilSurface(_depthstencil);
			else
			{
				if (surface != _depthstencil)
					_device->SetDepthStencilSurface(_depthstencil);

				surface->Release();
			}
		}

		size = _trfm.sd;

		//
		D3DVIEWPORT9 vp;
		vp.X = 0;
		vp.Y = 0;
		vp.Width = size.width;
		vp.Height = size.height;
		vp.MinZ = 0.0f;
		vp.MaxZ = 1.0f;
		_device->SetViewport(&vp);
	}

	//
	_vsize.Set((float)size.width, (float)size.height);

	_trfm.vp.loc.Set(0, 0);
	_trfm.vp.size = size;

	_trfm.otm = tpl::Mat4::OrthoLH(_vsize.width, _vsize.height, 0.0f, 1.0f);

	return true;
}

#endif	// _QG_USE_DX9
